//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public License
// along with this program.  If not, see http://www.gnu.org/licenses/.
// 

#include "BmacLayer.h"
#include "BMACPkt_m.h"
#include "SimpleAddress.h"
#include <MacToPhyControlInfo.h>
#include <PhyToMacControlInfo.h>
#include <NetwToMacControlInfo.h>
#include <MacToNetwControlInfo.h>

Define_Module(BmacLayer);

void BmacLayer::initialize(int stage)
{
    // TODO - Generated method body
	BaseMacLayer::initialize(stage);
	if(stage == 0)
	{
		queueLength = hasPar("queueLength") ? par("queueLength") : 10;
		animation = hasPar("animation") ? par("animation") : true;
		bitrate = hasPar("bitrate") ? par("bitrate") : 15360;
		headerLength = hasPar("headerLength") ? par("headerLength") : 10;
		slotDuration = hasPar("slotDuration") ? par("slotDuration") : 1;
		checkInterval = hasPar("checkInterval") ? par("checkInterval") : 0.1;
		txPower = hasPar("txPower") ? par("txPower") : 50;
		coreEV << "headerLength: " << headerLength << ", bitrate: " << bitrate << endl;
		stats = par("stats");



		nbTxDataPackets = 0;
		nbTxPreambles = 0;
		nbRxDataPackets = 0;
		nbRxPreambles = 0;
		nbDroppedDataPackets=0;

		updateMacState(SLEEP);
		droppedPacket.setReason(DroppedPacket::NONE);
	}
	else
	{
		myMacAddr = getParentModule()->getId();
		EV << "Scheduling the first wakeup at : " << slotDuration << endl;
		//define the the basic messages
		wakeup = new cMessage("wakeup");
		wakeup->setKind(BMAC_WAKEUP);


		timeoutData = new cMessage("timeoutData");
		timeoutData->setKind(BMAC_TIMEOUT_DATA);
		sendData = new cMessage("sendDate");
		sendData -> setKind(BMAC_SEND_DATA);

		stop_preambles = new cMessage("stop_preambles");
		stop_preambles->setKind(BMAC_STOP_PREAMBLES);
		send_preambles = new cMessage("send_preamble");
		send_preambles->setKind(BMAC_SEND_PREAMBLES);

		checkChannel = new cMessage("checkChannel");
		checkChannel->setKind(BMAC_CHECK_CHANNEL);

		scheduleAt(dblrand()*((double)slotDuration), wakeup);
	}

}

void BmacLayer::finish() {
    MacQueue::iterator it;
    if (!wakeup->isScheduled()) delete wakeup;
	if (!checkChannel->isScheduled()) delete checkChannel;
	if (!timeoutData->isScheduled()) delete timeoutData;
	if (!send_preambles->isScheduled()) delete send_preambles;
	if (!stop_preambles->isScheduled()) delete stop_preambles;

    for(it = macQueue.begin(); it != macQueue.end(); ++it) {
        delete (*it);
    }
    macQueue.clear();
}


void BmacLayer::sendPreamble()
{

	BMACPkt* preamble = new BMACPkt();
	preamble->setSrcAddr(myMacAddr);
	preamble->setDestAddr(L2BROADCAST);
	preamble->setKind(FRAME_PREAMBLES);
	preamble->setBitLength(headerLength);

	attachSignal(preamble);
	sendDown(preamble);
	nbTxPreambles++;

}

void BmacLayer::sendDataPacket()
{

	nbTxDataPackets++;
	MacPkt *pkt = macQueue.front()->dup();
	attachSignal(pkt);
	lastDataPktDestAddr = pkt->getDestAddr();
	pkt->setKind(FRAME_DATA);
	sendDown(pkt);
}


/**
 * Handle BMAC preambles and received data packets.
 */
void BmacLayer::handleLowerMsg(cMessage *msg)
{
    BMACPkt *mac = static_cast<BMACPkt *>(msg);
	EV << " I have received a MacMsg .\n";
	executeMac(EV_FRAME_RECEIVED,mac);

}

/**
 * Check whether the queue is not full: if yes, print a warning and drop the packet.
 * Then initiate sending of the packet, if the node is sleeping. Do nothing, if node is working.
 */
void BmacLayer::handleUpperMsg(cMessage *msg)
{
	bool pktAdded = addToQueue(msg);
	if (!pktAdded)
		return;
	// force wakeup now
	if (wakeup->isScheduled() && (macState == SLEEP))
	{
		cancelEvent(wakeup);
		scheduleAt(simTime() + dblrand()*checkInterval*5, wakeup);
	}

}

void BmacLayer::handleLowerControl(cMessage *msg)
{
	// Transmission of one packet is over
    if(msg->getKind() == MacToPhyInterface::TX_OVER) {
    	executeMac(EV_FRAME_TRANSMITTED,msg);
    }
    // Radio switching (to RX or TX) ir over, ignore switching to SLEEP.
    else if(msg->getKind() == MacToPhyInterface::RADIO_SWITCHING_OVER) {
    	// we just switched to TX after CCA, so simply send the first sendPremable self message
    	EV<< "control message: RADIO_SWITCHING_OVER" << endl;
    	executeMac(EV_RADIO_SWITCHED,msg);
    }
    else {
        coreEV << "control message with wrong kind -- deleting\n";
    }
    delete msg;
}

void BmacLayer::handleSelfMsg(cMessage *msg)
{
	switch(msg->getKind())
	{
	case BMAC_WAKEUP:
		executeMac(EV_TIMER_WAKEUP,msg);
		break;
	case BMAC_SEND_PREAMBLES:
		executeMac(EV_FRAME_TRANSMITTED,msg);
		break;
	case BMAC_STOP_PREAMBLES:
		executeMac(EV_PREAMBLES_TIMEOUT,msg);
		break;
	case BMAC_CHECK_CHANNEL:
		executeMac(EV_CCA_TIMEOUT,msg);
		break;
	case BMAC_SEND_DATA:
		executeMac(EV_DATA_SEND,msg);
		break;
	case BMAC_TIMEOUT_DATA:
		executeMac(EV_DATA_TIMEOUT,msg);
		break;
	default:
		break;


	}
    // TODO - Generated method body
}

void BmacLayer::executeMac(t_mac_event event, cMessage *msg)
{
		switch(macState) {

		case SLEEP:
			updateStatusSleep(event,msg);
			break;
		case RX:
			updateStatusRx(event,msg);
			break;
		case CCA:
			updateStatusCCA(event,msg);
			break;
		case SEND_PREAMBLE:
			updateStatusSD_PRM(event,msg);
			break;
		case SEND_DATA:
			updateStatusSendData(event,msg);
			break;
		case WAIT_TX_DATA_OVER:
			updateStatusWaitTxOver(event,msg);
			break;
		default:
			EV << "Error in CSMA FSM: an unknown state has been reached. macState=" << macState << endl;
		}
}


void  BmacLayer::updateStatusSleep(t_mac_event event, cMessage *msg)
{
	switch(event)
	{
		//Update MacStat to CCA after set the redio to RX
		case EV_RADIO_SWITCHED:
		{
			updateMacState(CCA);
			//I set the CheckInterval to be a random number to aviod syn error
			scheduleAt(simTime() + checkInterval*2, checkChannel);
			break;
		}
		case EV_TIMER_WAKEUP:
		{
			EV << "Schedule WAKEUP ,Next State is CCA " << endl ;
			if (wakeup->isScheduled())
				cancelEvent(wakeup);
			phy->setRadioState(Radio::RX);
			break;
		}
		default:
		{
			EV << "There may be some thing wrong ,Bmac is into a abnormal state!" << endl;
			break;
		}

	}
	return;

}

void  BmacLayer::updateStatusCCA(t_mac_event event, cMessage *msg)
{
	// if we are listening to the channel and receive a preamble, wait for the data
	BMACPkt *mac = static_cast<BMACPkt *>(msg);
	switch(event)
	{
		case EV_RADIO_SWITCHED:
		{
			if(phy->getRadioState() == Radio::TX)
			{
				updateMacState(SEND_PREAMBLE);
				scheduleAt(simTime()+ checkInterval,send_preambles);
				//Bmac need more than slotDuration to make sure that the preambles will be received by neighbor node
				scheduleAt(simTime() + slotDuration+checkInterval*5, stop_preambles);
			}
			else if(phy->getRadioState() == Radio::SLEEP)
			{
				scheduleAt(simTime() + slotDuration, wakeup);
				updateMacState(SLEEP);
			}
			break;
		}
		case EV_FRAME_RECEIVED:
		{
			if(mac->getKind() == FRAME_PREAMBLES)
			{
				cancelEvent(checkChannel);
				nbRxPreambles++;
				EV << "State CCA, message BMAC_PREAMBLE received, new state WAIT_DATA" << endl;
				updateMacState(RX);
				scheduleAt(simTime() + slotDuration*2, timeoutData);
			}
			break;
		}

		case EV_CCA_TIMEOUT:
		{
			EV << "CCA timeout ,so the channel is clean ,We can sent message now" << endl;
			if(checkChannel->isScheduled())
				cancelEvent(checkChannel);

			if (macQueue.size() > 0)
			{
				coreEV << "State CCA, message CCA_TIMEOUT, new state SEND_PREAMBLE" << endl;
				phy->setRadioState(Radio::TX);
			}
			// if not, go back to sleep and wake up after a full period
			else
			{
				coreEV << "State CCA, message CCA_TIMEOUT, new state SLEEP" << endl;
				phy->setRadioState(Radio::SLEEP);

			}
			break;
		}
		default:
		{
			EV << "Some thing wrong ,Bmac is into a abnormal state!" << endl;
			break;
		}

	}//switch;
}

void BmacLayer::updateStatusRx(t_mac_event event,cMessage *msg)
{

	switch(event)
	{
		case EV_RADIO_SWITCHED:
		{
			if (macQueue.size() > 0)
			{
				EV << "Something in the queue. Waking up soon again\n";
				scheduleAt(simTime() + dblrand()*checkInterval*5, wakeup);
			}
			else
			{
				EV << "Nothing in the queue. Waking up after a full slot\n";
				scheduleAt(simTime() + dblrand()*((double)slotDuration), wakeup);
			}
			updateMacState(SLEEP);
			break;
		}
		//Can't recieve Data During Wait Time,so Back to sleep
		case EV_DATA_TIMEOUT:
		{
			EV << "Can't recieve Data During Wait Time,so Back to sleep" << endl;
			coreEV << "State WAIT_DATA, message BMAC_DATA_TIMEOUT, new state SLEEP" << endl;
			// if something in the queue, wakeup soon.
			updateMacState(SLEEP);
			phy->setRadioState(Radio::SLEEP);
			return;
		}

		case EV_FRAME_RECEIVED:
		{

			if (msg->getKind() == FRAME_DATA) {
				if (timeoutData->isScheduled())
					cancelEvent(timeoutData);
				nbTxDataPackets++;
				BMACPkt *mac = static_cast<BMACPkt *>(msg);
				int dest = mac->getDestAddr();
				if(dest == myMacAddr || dest == L2BROADCAST)
				{
					EV << "sending pkt to upper...\n";
					sendUp(decapsMsg(mac));
					bubble("recevie DATA");
				}
				else {
					EV << "packet not for me, deleting...\n" << endl;
					delete mac;
				}
				phy->setRadioState(Radio::SLEEP);
				break;

			}
		}
	default:
		{
			break;
		}
	}

}

void BmacLayer::updateStatusSD_PRM(t_mac_event event,cMessage *msg)
{
	switch(event)
	{
	case EV_FRAME_TRANSMITTED:
		{
			if(msg->getKind() == MacToPhyInterface::TX_OVER)
			{
				cancelEvent(send_preambles);
				scheduleAt(simTime() + checkInterval, send_preambles);
			}
			else if (msg->getKind() == BMAC_SEND_PREAMBLES)
			{
				if(phy->getRadioState() == Radio::TX)
					sendPreamble();
			}
			break;
		}


	case EV_PREAMBLES_TIMEOUT:
		{
			EV<<"Send PREAMBLE OK , go to Send DATA"<<endl;
			cancelEvent(send_preambles);
			cancelEvent(stop_preambles);
			updateMacState(SEND_DATA);
			scheduleAt(simTime(), sendData);
			txAttempts = 1;
			break;

		}

	default:
		{
			EV << "Some thing wrong ,Bmac is into a abnormal state!" << endl;
			break;
		}
	}
}



void BmacLayer::updateStatusSendData(t_mac_event event,cMessage *msg)
{
	switch(event)
		{
			case EV_DATA_SEND:
			{
				coreEV << "State SEND_DATA, message BMAC_SEND_PREAMBLE or BMAC_RESEND_DATA, new state WAIT_TX_DATA_OVER" << endl;
				// send the data packet
				sendDataPacket();
				bubble("Send Data");
				updateMacState(WAIT_TX_DATA_OVER);
				break;
			}
			default:
			{
				EV << "Some thing wrong ,Bmac is into a abnormal state!" << endl;
				break;
			}
		}
		return;
}

void BmacLayer::updateStatusWaitTxOver(t_mac_event event,cMessage *msg)
{
	switch(event)
	{
		case EV_RADIO_SWITCHED:
		{
			if (macQueue.size() > 0)
			{
				EV << "Something in the queue. Waking up soon again\n";
				scheduleAt(simTime() + dblrand()*checkInterval*5, wakeup);
			}
			else
			{
				EV << "Nothing in the queue. Waking up after a full slot\n";
				scheduleAt(simTime() + dblrand()*((double)slotDuration), wakeup);
			}
			updateMacState(SLEEP);
			break;
		}

		case EV_FRAME_TRANSMITTED:
		{

			EV << "State WAIT_TX_DATA_OVER, message BMAC_DATA_TX_OVER, new state  SLEEP" << endl;
			macQueue.pop_front();
			// if something in the queue, wakeup soon.
			phy->setRadioState(Radio::SLEEP);
			break;

		}
		default:
		{
			EV << "Some thing wrong ,Bmac is into a abnormal state!" << endl;
			break;
		}

	}
	return;

}


/**
 * Encapsulates the received network-layer packet into a MacPkt and set all needed
 * header fields.
 */
bool BmacLayer::addToQueue(cMessage *msg)
{
	BMACPkt *macPkt = new BMACPkt(msg->getName());
	macPkt->setBitLength(headerLength);
	NetwToMacControlInfo* cInfo = static_cast<NetwToMacControlInfo*> (msg->removeControlInfo());
	//int dest = cInfo->getNextHopMac();
	int dest = L2BROADCAST;
	macPkt->setDestAddr(dest);
	delete cInfo;
	macPkt->setSrcAddr(myMacAddr);

	assert(static_cast<cPacket*>(msg));
	macPkt->encapsulate(static_cast<cPacket*>(msg));

	if (macQueue.size() < queueLength) {
		macQueue.push_back(macPkt);
		coreEV << "Max queue length: " << queueLength << ", packet put in queue\n  queue size: " << macQueue.size() << " macState: " << macState << endl;
		return true;
	}
	else {
		// queue is full, message has to be deleted
		EV << "New packet arrived, but queue is FULL, so new packet is deleted\n";
		msg->setName("MAC ERROR");
		msg->setKind(PACKET_DROPPED);
		sendControlUp(msg);
		droppedPacket.setReason(DroppedPacket::QUEUE);
		utility->publishBBItem(catDroppedPacket, &droppedPacket, nicId);
		nbDroppedDataPackets++;
	}
	return false;

}




void BmacLayer::attachSignal(MacPkt *macPkt)
{
	//calc signal duration
	simtime_t duration = macPkt->getBitLength() / bitrate;
	//create signal
	Signal* s = createSignal(simTime(), duration, txPower, bitrate);
	//create and initialize control info
	MacToPhyControlInfo* ctrl = new MacToPhyControlInfo(s);
	macPkt->setControlInfo(ctrl);
}

void BmacLayer::updateMacState(States newMacState) {
	macState = newMacState;
	EV<<"newState = " <<State2Name(macState) <<endl;
}

char* BmacLayer::State2Name(States state)
{
	switch(state)
	{
	case SLEEP:
		return "SLEEP";
	case RX:  	//Receive Data
		return "RX";
	case CCA:	//Receive Preamble
		return "CCA";
	case SEND_PREAMBLE:
		return "SEND_PREAMBLE";
	case SEND_DATA:
		return "SEND_DATA";
	case  WAIT_TX_DATA_OVER:
		return "WAIT_TX_DATA_OVER";
	}
}
